/**
 *
 */
package stc.skymobi.netty.handler.codec.access;

import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.skymobi.common.EncryptedUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import stc.skymobi.bean.bytebean.core.BeanFieldCodec;
import stc.skymobi.bean.bytebean.core.DecContext;
import stc.skymobi.bean.esb.annotation.EsbSignal;
import stc.skymobi.bean.esb.core.EsbHeaderable;
import stc.skymobi.bean.util.meta.Int2TypeMetainfo;
import stc.skymobi.netty.handler.codec.access.protocol.AccessFixedHeader;
import stc.skymobi.netty.handler.codec.access.protocol.AccessHeaderWithSeqNum;
import stc.skymobi.netty.handler.codec.access.protocol.signal.AbstractEsbT2ASignal;
import stc.skymobi.netty.handler.codec.access.protocol.signal.EsbAccess2TerminalSignal;
import stc.skymobi.netty.handler.codec.access.protocol.signal.EsbTerminal2AccessSignal;
import stc.skymobi.netty.handler.codec.access.protocol.signal.RegisterToAccessReq;

import java.util.Arrays;


/**
 * @author Marvin.Ma
 */
public class AccessSignalCodecUtils {

    private static final Logger logger =
            LoggerFactory.getLogger(AccessSignalCodecUtils.class);

    /* msgCode 定义 */
    public final static int ESB_MSG_CODE_REGISTER_ACCESS_RESP = 0x9804;           /* 终端接入access应答。 */

    public static AccessFixedHeader decodeAccessHeader(
            byte[] headerBytes,
            BeanFieldCodec esbBeanCodec) throws Exception {
        if (headerBytes.length < AccessFixedHeader.FIXED_HEADER_SIZE) {
            return null;
        }

        AccessFixedHeader header = null;

        if (headerBytes.length == AccessHeaderWithSeqNum.WITH_SEQ_NUM_HEADER_SIZE) {
            header = (AccessFixedHeader) esbBeanCodec.decode(
                    esbBeanCodec.getDecContextFactory().createDecContext(
                            headerBytes, AccessHeaderWithSeqNum.class, null, null)).getValue();
        } else if (headerBytes.length == AccessFixedHeader.WITHOUT_SEQ_NUM_HEADER_SIZE) {
            header = (AccessFixedHeader) esbBeanCodec.decode(
                    esbBeanCodec.getDecContextFactory().createDecContext(
                            headerBytes, AccessFixedHeader.class, null, null)).getValue();
        } else {
            logger.warn("can't decode AccessHeader with {} bytes", headerBytes.length);
        }

        return header;
    }

    public static EsbAccess2TerminalSignal decodeSignalBody(
            AccessFixedHeader fixedHdr,
            byte[] bodyBytes,
            BeanFieldCodec esbBeanCodec,
            Int2TypeMetainfo esbTypeMetainfo) throws Exception {

        switch (fixedHdr.getMsgCode()) {
            case ESB_MSG_CODE_REGISTER_ACCESS_RESP:
                return decodeUaResponse(fixedHdr, bodyBytes, esbBeanCodec, esbTypeMetainfo);
            default:
                return decodeAccess2Terminal(fixedHdr, bodyBytes, esbBeanCodec, esbTypeMetainfo);
            //logger.error("we don't know how to decode msgCode {}",  fixedHdr.getMsgCode());
        }

        //return null;
    }

    private static EsbAccess2TerminalSignal decodeAccess2Terminal(
            AccessFixedHeader fixedHdr,
            byte[] bytes,
            BeanFieldCodec esbBeanCodec,
            Int2TypeMetainfo esbTypeMetainfo) {
        /*DecResult rslt = esbBeanCodec.decode(
                esbBeanCodec.getDecContextFactory().createDecContext(
                        bytes, AccessRespHeader.class, null, null));

        AccessRespHeader a2tHdr = (AccessRespHeader) rslt.getValue();

        if (logger.isTraceEnabled()) {
            logger.trace("m2a header {}", a2tHdr);
        }

        //	message body bytes
        bytes = rslt.getRemainBytes();*/

        EsbAccess2TerminalSignal signal = null;
        Class<?> type = esbTypeMetainfo.find(fixedHdr.getMsgCode());
        if (null == type) {
            throw new RuntimeException("unknow message code for t2a:0x"
                    + Integer.toHexString(fixedHdr.getMsgCode()).toUpperCase() + "(" + fixedHdr.getMsgCode() + ")");
        }

        DecContext decCtx = esbBeanCodec.getDecContextFactory().createDecContext(
                bytes, type, null, null);

        //	支持在EsbDecoder中尽可能早的进行hdr相关赋值
        decCtx.setProperty(EsbHeaderable.class.getSimpleName(), fixedHdr);

        signal = (EsbAccess2TerminalSignal) esbBeanCodec.decode(decCtx).getValue();

        return signal;
    }

    /**
     * UA响应解码
     */
    private static EsbAccess2TerminalSignal decodeUaResponse(
            AccessFixedHeader fixedHdr,
            byte[] bytes,
            BeanFieldCodec esbBeanCodec,
            Int2TypeMetainfo esbTypeMetainfo) {
        //UA重定向
        if (bytes[0] == 2) {
            fixedHdr.setMsgCode(0x98041);
        }
        return decodeAccess2Terminal(fixedHdr, bytes, esbBeanCodec, esbTypeMetainfo);
    }


    public static byte[] encodeSignal(
            EsbHeaderable signal,
            BeanFieldCodec esbBeanCodec,
            int myESBAddr) throws Exception {

        if (null == signal || !signal.checkIntegrity()) {
            logger.error("invalid signal {}", signal);

            throw new RuntimeException("invalid signal, signal is null or checkIntegrity failed");
        }

        byte[] bytes = null;
        if (signal instanceof EsbTerminal2AccessSignal) {
            bytes = encodeTerminal2Access((EsbTerminal2AccessSignal) signal, esbBeanCodec, myESBAddr);
        } else {
            throw new RuntimeException("encode: bean " + signal
                    + " is not EsbTerminal2AccessSignal.");
        }

        return bytes;
    }

    private static byte[] encodeTerminal2Access(
            EsbTerminal2AccessSignal signal,
            BeanFieldCodec esbBeanCodec,
            int myESBAddr) throws Exception {
        //	once
        if (signal instanceof RegisterToAccessReq) {
            RegisterToAccessReq req = (RegisterToAccessReq) signal;
            req.setLength((short) (73 + 18 + req.getHsman().length() + req.getHstype().length() - 7));
        }
        byte[] bytesBody =
                esbBeanCodec.encode(
                        esbBeanCodec.getEncContextFactory().createEncContext(
                                signal, signal.getClass(), null));
        if (signal instanceof RegisterToAccessReq) {
            return bytesBody;
        }

        EsbSignal attr = signal.getClass().getAnnotation(EsbSignal.class);
        if (null == attr) {
            throw new RuntimeException("invalid esb signal, bcs of no messageCode.");
        }
        //	create esb terminal2access header
        if ((signal.getFlags() & 0x0100) != 0x0100) {
            throw new RuntimeException("can not encode t2a message without seqNum");
        }
        AccessHeaderWithSeqNum fixedHdr = new AccessHeaderWithSeqNum();
        fixedHdr.setDstESBAddr(signal.getDstESBAddr());
        fixedHdr.setFlags((short) signal.getFlags());
        fixedHdr.setMsgCode(attr.messageCode());
        fixedHdr.setSeqNum(signal.getSeqNum());
        fixedHdr.setAck(signal.getAck());
        fixedHdr.setSrcESBAddr((0 == signal.getSrcESBAddr()) ? myESBAddr : signal.getSrcESBAddr());
        byte[] bytesHeader = esbBeanCodec.encode(
                esbBeanCodec.getEncContextFactory().createEncContext(
                        fixedHdr, AccessFixedHeader.class, null));

        ByteBuf buffer = Unpooled.buffer();
        buffer.writeBytes(bytesHeader, 0, AccessFixedHeader.FIXED_HEADER_SIZE); //access头的前8个字节不加密
        int messageSize = 8;

        //encrypt body
        if (signal instanceof AbstractEsbT2ASignal && ((AbstractEsbT2ASignal) signal).getEncryptKey() != 0) {
            byte[] encryptKey = Ints.toByteArray(((AbstractEsbT2ASignal) signal).getEncryptKey());
            bytesBody = EncryptedUtils.encode(encryptKey, Bytes.concat(Arrays.copyOfRange(bytesHeader, 8, bytesHeader.length), bytesBody));
        } else {
            buffer.writeBytes(bytesHeader, 8, AccessHeaderWithSeqNum.WITH_SEQ_NUM_HEADER_SIZE - AccessFixedHeader.FIXED_HEADER_SIZE); //不加密包的时候把access剩余头的字节写进去
            messageSize += (AccessHeaderWithSeqNum.WITH_SEQ_NUM_HEADER_SIZE - AccessFixedHeader.FIXED_HEADER_SIZE);
        }

        buffer.writeBytes(bytesBody);   //写入包体(加密的包体包含部分加密的头信息,不加密的包体不包含任何头信息)
        buffer.writerIndex(4);          //设置包长度所在字节的位移(5-6字节)
        messageSize += bytesBody.length;
        buffer.writeShort(messageSize);
        buffer.writerIndex(messageSize);

        //fixedHdr.setLength((short) (headerSize +));

        byte[] bytes = new byte[messageSize];
        buffer.readBytes(bytes);//ArrayUtils.addAll(bytesHeader, bytesBody);
        return bytes;
    }
}
